#include "PreCompile.hpp"
#include "CLangModule.hpp"
#include "PkgConfig.hpp"

#include "CCompilerBackend.hpp"

namespace BuildNG
{
  static const char C_MODULE_XML_NAME[] = "c";
  static const char CPP_MODULE_XML_NAME[] = "cpp";

  //////////
  // CLangModule
  CLangModule::CLangModule()
  {
    input_files = Glib::RefPtr<InputFilesSet>(new InputFilesSet);
    output_type = OutputType_Executable;
  }

  void CLangModule::load_from_xml(const Xml::Element &xml)
  {
    input_files->clear();
    
    set_name(xml.get_attribute_value("name"));

    // read the sources elements
    Xml::ConstElementList sources_xml = xml.get_child_elements("sources");
    for(int i = 0;i < (int)sources_xml.size();i++)
    {
      const Xml::Element *cur_elem = sources_xml[i];
      assert(cur_elem);

      input_files->add_group_from_xml(*cur_elem);
    }
    
    // read the references elements
    Xml::ConstElementList references_xml = xml.get_child_elements("references");
    for(int i = 0;i < (int)references_xml.size();i++)
    {
      const Xml::Element *cur_elem = references_xml[i];
      assert(cur_elem);
        
      read_references(*cur_elem);
    }
  }
  
  void CLangModule::read_references(const Xml::Element &xml)
  {
    Xml::ConstElementList children = xml.get_child_elements();
    for(int i = 0;i < (int)children.size();i++)
    {
      const Xml::Element *cur_elem = children[i];
      assert(cur_elem);

      if(cur_elem->get_name() == "package")
      {
        Glib::RefPtr<CLangReference> reference(new CLangReference);
        reference->reference_type = CLangReference::ReferenceType_Package;
        reference->name = cur_elem->get_attribute_value("name");
        references.push_back(reference);
      }
      else
        throw Exception("Unknown reference type:" + xml.get_name());
    }    
  }

  void CLangModule::save_to_xml(Xml::Element &xml) const
  {
    assert(!"Not implemented yet");
  }

  bool CLangModule::build(BuildContext &context,Glib::RefPtr<ErrorListener> error_listener)
  {
    CLangBuildContext clang_context;
    bool build_successful = true;

    if(!build_source_files(context,clang_context,error_listener))
      return false;
    if(!build_object_files(context,clang_context,error_listener))
      return false;

    return true;
  }

  bool CLangModule::build_source_files(
    BuildContext &context,
    CLangBuildContext &clang_context,
    Glib::RefPtr<ErrorListener> error_listener)
  {
    Glib::RefPtr<CCompilerBackend> backend = get_backend();
    
    // set the references in the backend
    for(int i = 0;i < (int)references.size();i++)
      backend->add_reference(references[i]);

    bool success = true;

    InputFilesSet::Iterator it = input_files->get_input_files_entry_iterator();
    while(it.move_next())
    {
      Glib::RefPtr<InputFiles> cur_input_files = it.get_entry();
      
      std::vector<Glib::ustring> cur_phys_files = 
        cur_input_files->build_physical_files(context);
      for(int j = 0;j < (int)cur_phys_files.size();j++)
      {
        CLangSourceFileType source_type = clang_source_file_type_by_ext(cur_phys_files[j]);
        switch(source_type)
        {
        case CLangSourceFileType_CSource:
          {
            backend->set_language(CCompilerBackend::Language_PlainC);

            Glib::ustring obj_file_name;
            bool file_successful = backend->build_source_file(context,
              cur_phys_files[j],obj_file_name,error_listener);
            if(file_successful)
            {
              assert(obj_file_name != "");
              clang_context.object_files.push_back(obj_file_name);
            }
            success = success && file_successful;
          }
          break;

        case CLangSourceFileType_CppSource:
          if(supports_cpp())
          {
            backend->set_language(CCompilerBackend::Language_CPlusPlus);

            Glib::ustring obj_file_name;
            bool file_successful = backend->build_source_file(context,
              cur_phys_files[j],obj_file_name,error_listener);
            if(file_successful)
            {
              assert(obj_file_name != "");
              clang_context.object_files.push_back(obj_file_name);
            }
            success = success && file_successful;
          }
          else
          {
            ErrorLocationInfo loc_info;
            if(!cur_input_files->get_xml_element_location_info(loc_info))
              loc_info.set_not_specified();
            error_listener->report_error(loc_info,"C++ files cannot be part of a C module.");
            success = false;
          }
          break;

        case CLangSourceFileType_Header:
        case CLangSourceFileType_Unknown:
          // nothing
          break;

        default:
          assert(!"Unknown source type");
          break;
        }
      }
    }
    
    return success;
  }

  bool CLangModule::build_object_files(
    BuildContext &context,
    CLangBuildContext &clang_context,
    Glib::RefPtr<ErrorListener> error_listener)
  {
    Glib::RefPtr<CCompilerBackend> backend = get_backend();

    // set the references in the backend
    for(int i = 0;i < (int)references.size();i++)
      backend->add_reference(references[i]);

    switch(output_type)
    {
    case OutputType_Executable:
      return backend->link(context,clang_context.object_files,error_listener);

    case OutputType_SharedLibrary:
      assert(!"not implemented yet");
      break;

    case OutputType_Library:
      assert(!"not implemented yet");
      break;
    
    default:
      assert(!"invalid output type");
      break;
    }

    return false;
  }

  Glib::RefPtr<CCompilerBackend> CLangModule::get_backend()
  {
    Glib::ustring backend_name = Settings::get_setting_string(
      "/buildng-settings/c-compiler/compiler");
    return CCompilerBackend::get_compiler_backend(backend_name);
  }

  //////////
  // PlainCModule
  void PlainCModule::init()
  {
    ModuleTypeInfo type_info;
    type_info.create_func = ModuleTypeInfo::ctor_wrapper<PlainCModule>;
    register_module_type(C_MODULE_XML_NAME,type_info);
  }

  void PlainCModule::shutdown()
  {
  }

  bool PlainCModule::supports_cpp() const
  {
    return false;
  }

  ////////
  // CppModule
  void CppModule::init()
  {
    ModuleTypeInfo type_info;
    type_info.create_func = ModuleTypeInfo::ctor_wrapper<CppModule>;
    register_module_type(CPP_MODULE_XML_NAME,type_info);
  }

  void CppModule::shutdown()
  {
  }

  bool CppModule::supports_cpp() const
  {
    return true;
  }

  CLangSourceFileType clang_source_file_type_by_ext(const Glib::ustring &file_name)
  {
    Glib::ustring::size_type dot_index = file_name.find_last_of(".");
    if(dot_index == Glib::ustring::npos)
      return CLangSourceFileType_Unknown;   // no extension.

    Glib::ustring extension = file_name.substr(dot_index + 1);
    
     // cvt it to lower case.
    gchar *tmp = g_utf8_strdown(extension.c_str(),-1);
    extension = tmp;
    g_free(tmp);

    if(extension == "c")
      return CLangSourceFileType_CSource;
    else if(extension == "cc" || extension == "cpp")
      return CLangSourceFileType_CppSource;
    else if(extension == "h" || extension == "hpp")
      return CLangSourceFileType_Header;
    else
    {
      // unknown extension
      return CLangSourceFileType_Unknown;
    }
  }


}

